package kalambury.srv;

import kalambury.db.ScoreEntity;
import kalambury.db.UserEntity;
import kalambury.srv.packets.*;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.Query;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.Collection;
import java.util.List;

/**
 * Created with IntelliJ IDEA.
 * User: Administrator
 * Date: 06.11.12
 * Time: 15:58
 * To change this template use File | Settings | File Templates.
 */

/**
 * klasa obsługująca jednego klienta po TCP+SSL
 */
public class ClientThread implements Runnable {
    private ObjectOutputStream outStream;
    private ObjectInputStream inStream;
    private Socket client;

    private String myName = null;
    private boolean stop = false;
    private boolean loggedin;

    private ScoreEntity score;

    private static EntityManagerFactory emf = Persistence.createEntityManagerFactory("NewPersistenceUnit");
    private static EntityManager em = emf.createEntityManager();

    public ClientThread(Socket client) {
        this.client = client;
    }

    /**
     * główna pętla obsługi klienta
     */
    @Override
    public void run() {
        loggedin = false;
        System.out.println("connection from " + client.getRemoteSocketAddress().toString());
        try {
            inStream = new ObjectInputStream(client.getInputStream());
            outStream = new ObjectOutputStream(client.getOutputStream());

            while (!stop) {
                //System.out.println("logged="+loggedin);
                Packet pkt = (Packet) inStream.readObject();
                handleServerPacket(pkt);
            }
        } catch (Exception e) {
            if (myName != null) {
                System.out.println("player " + myName + " crashed");
                ServerImpl.state.players.remove(myName);
                try {
                    pickNewDrawer();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            System.out.println(e.getMessage());
            //e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
        System.out.println("ended connection with: " + client.getRemoteSocketAddress().toString());
        try{
            client.close();
        }                  catch (Exception e){}
    }


    /**
     * funkcja obsługuje pakiety przychodzące od klienta i podejmuje odpowiednie akcje
     * @param pkt obiekt pakietu
     * @throws Exception
     */
    private void handleServerPacket(Packet pkt) throws Exception {
        System.out.println(pkt.toString());
        //HELLO - logowanie, sprawdzenie hasla, rozeslanie informacji o graczach
        if (pkt instanceof HelloPacket) {
            HelloPacket hello = (HelloPacket) pkt;
            HelloResponsePacket hrp = new HelloResponsePacket();
            hrp.success = true;
            System.out.println("login request: " + hello.name + " / " + hello.pass);
            if (loggedin) {
                System.out.println("already logged in as " + myName);
                hrp.message = "already logged in as " + myName;
                hrp.success = false;
                outStream.writeObject(hrp);
                return;
            }
            if (ServerImpl.state.players.containsKey(hello.name)) {
                hrp.message = "user already logged";
                hrp.success = false;
                outStream.writeObject(hrp);
                client.close();
                stop = true;
                return;
            }
            myName = hello.name;
            String queryString = "SELECT * FROM user WHERE username = :user and password = :pass";
            Query query = em.createNativeQuery(queryString);
            query.setParameter("user", hello.name.toLowerCase());
            query.setParameter("pass", hello.pass);
            System.out.println("size=" + query.getResultList().size());
            if (query.getResultList().size() == 0) {
                hrp.message = "user/pass not valid";
                hrp.success = false;
                outStream.writeObject(hrp);
                client.close();
                stop = true;
                return;
            }
            System.out.println("player " + myName + " entered");
            loggedin = true;
            initializeScore();
            ServerImpl.state.players.put(hello.name, new UserSocket(client, inStream, outStream, myName));
            getUser(myName).status = (getUsers().size() == 1) ? PlayerStatus.Drawer : PlayerStatus.Observer;
            outStream.writeObject(hrp);
            sendPlayersListToAll();
        } else if (!loggedin) {
            // WSZYSTKIE POZOSTALE PAKIETY IGNOROWANE JESLI NIEZALOGOWANY
            return;
        //BYE - usuniecie danych o kliencie, rozeslanie informacji o graczach
        } else if (pkt instanceof ByePacket) {
            ByePacket bye = (ByePacket) pkt;
            System.out.println("player " + myName + " left");
            ServerImpl.state.players.remove(myName);
            sendPlayersListToAll();
            client.close();
            stop = true;
            pickNewDrawer();
        //BROADCASTMESSAGE - rozeslac do wszystkich prócz nadawcy
        } else if (pkt instanceof BroadcastMessagePacket) {
            forwardToAllButMe(pkt);
        //IMAGE - rozeslac do wszystkich prócz nadawcy
        } else if (pkt instanceof ImagePacket) {
            forwardToAllButMe(pkt);
        //SUGGESTION - weryfikacja rozwiazania, obsluga wygranej
        } else if (pkt instanceof SuggestionPacket) {
            SuggestionPacket sp = (SuggestionPacket) pkt;
            SuggestionResultPacket resultPacket = new SuggestionResultPacket();
            resultPacket.author = myName;
            resultPacket.suggestionString = sp.suggestionString;
            resultPacket.valid = sp.suggestionString.equals(ServerImpl.state.currentPass);
            sendToAll(resultPacket);
            if (resultPacket.valid) {
                // KTOS WYGRAL
                UserSocket du = getDrawingUser();
                UserSocket wonUser = setDrawingUser(myName);
                wonUser.status = PlayerStatus.Drawer;
                if (du != null) {
                    notifyUserStatus(du);
                }
                ServerImpl.state.pickNewPassword();

                score.incPoints();
                saveUserPoints();

                notifyUserStatus(wonUser);

                DrawerChangedPacket dcp = new DrawerChangedPacket(resultPacket.author);
                sendToAll(dcp);
                ;
                sendPlayersListToAll();
            }
        //CUSTOMREQUEST - dodatkowe pakiety
        } else if (pkt instanceof CustomRequestPacket) {
            CustomRequestPacket crp = (CustomRequestPacket) pkt;
            switch (crp.requestType) {
                case ResendPlayerStatus: //wyslij ponownie stan gracza
                    if (myName == null)
                        return;
                    notifyUserStatus(getThisUser());
                    break;
                case ResendPlayersList:  //wyslij ponownie liste graczy
                    sendPlayersListToAll();
                    break;
                default:
                    break;
            }
        }
    }

    private void saveUserPoints() {

        em.getTransaction().begin();
        em.merge(score);
        em.flush();
        em.getTransaction().commit();
    }

    private void initializeScore() {
        score = new ScoreEntity();

        String queryString = "select max(s.id) from ScoreEntity s";
        Query query = em.createQuery(queryString);
        Number maxId = (Number) query.getSingleResult();


        int lastId = (maxId!=null) ? maxId.intValue() : 0 ;
        score.setId(lastId + 1);

        UserEntity userEntity = getUserEntityFromUsername(myName);
        score.setUserByUser(userEntity);

        saveUserPoints();
    }

    private static UserEntity getUserEntityFromUsername(String username) {

        UserEntity userEntity = null;
        String queryString = "select u from UserEntity u WHERE u.username = :user";
        Query query = em.createQuery(queryString).setParameter("user", username);
        userEntity = (UserEntity) query.getSingleResult();
        return userEntity;
    }

    /**
     * losuje nowego gracza rysującego
     * @throws Exception
     */
    private static void pickNewDrawer() throws Exception {
        ServerImpl.state.pickNewPassword();

        UserSocket wonUser = null;
        for (UserSocket u : getUsers()) {
            wonUser = u;
            break;
        }

        if (wonUser == null)
            return;

        wonUser.status = PlayerStatus.Drawer;
        notifyUserStatus(wonUser);

        DrawerChangedPacket dcp = new DrawerChangedPacket(wonUser.name);
        sendToAll(dcp);
        sendPlayersListToAll();
    }

    /**
     * przesyła pakiet do wszystkich poza orginalnym nadawcą
     * @param p pakiet do rozesłania
     */
    private void forwardToAllButMe(Packet p) {
        for (UserSocket us : getUsers()) {
            if (us.name.equals(myName))
                continue;
            try {
                us.out.writeObject(p);
            } catch (IOException e) {
                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            }
        }
    }

    /**
     * rozsyła pakiet do wszystkich
     * @param p pakiet do rozesłania
     */
    private static void sendToAll(Packet p) {
        for (UserSocket us : getUsers()) {
            try {
                us.out.writeObject(p);
            } catch (IOException e) {
                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            }
        }
    }

    /**
     * wysyła do wszystkich klientów aktualną listę graczy
     */
    private static void sendPlayersListToAll() {
        PlayerListPacket plp = new PlayerListPacket();
        for (UserSocket us : getUsers()) {


            String queryString = "select s from ScoreEntity s where s.userByUser.username = :name order by s.id desc";
            Query query = em.createQuery(queryString);
            query.setParameter("name", us.name);

            int score = 0;
            List scores = query.getResultList();
            if (scores != null && scores.size() > 0) {
                ScoreEntity lastScore = (ScoreEntity) scores.get(0);
                score = lastScore.getPoints();
            }

            PlayerInfo pi = new PlayerInfo(us.name, us.status, score);
            plp.players.add(pi);
        }
        for (UserSocket us : getUsers()) {
            try {
                us.out.writeObject(plp);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * wysyła pakiet do klienta
     * @param p pakiet do wysłania
     */
    private void sendPacket(Packet p) {
        try {
            if (outStream != null)
                outStream.writeObject(p);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * powiadamia wybranego gracza o jego stanie
     * @param u obiekt reprezentujący danego użytkownika
     * @throws Exception
     */
    private static void notifyUserStatus(UserSocket u) throws Exception {
        PlayerStatusPacket psp = new PlayerStatusPacket();
        psp.playerStatus = u.status;

        UserSocket du = getDrawingUser();
        String nm = du == null ? "" : du.name;
        psp.additionalInfo = (u.status == PlayerStatus.Drawer) ? ServerImpl.state.currentPass : nm;
        u.sendPacket(psp);
    }

    /**
     * pobierz wszystkich graczy
     * @return kolekcja obiektów wszystkich użytkowników
     */
    private static Collection<UserSocket> getUsers() {
        return ServerImpl.state.players.values();
    }

    /**
     * pobierz wybranego użytkownika
     * @param key nazwa użytkownika
     * @return obiekt reprezentujący danego użytkownika
     */
    private static UserSocket getUser(String key) {
        return ServerImpl.state.players.get(key);
    }

    /**
     * pobierz rysującego użytkownika
     * @return obiekt reprezentujący danego użytkownika
     */
    private static UserSocket getDrawingUser() {
        for (UserSocket u : getUsers()) {
            if (u.status == PlayerStatus.Drawer)
                return u;
        }
        return null;
    }

    /**
     * ustaw użytkownika jako rysującego
     * @param key nazwa użytkownika
     * @return obiekt reprezentujący danego użytkownika
     */
    private static UserSocket setDrawingUser(String key) {
        UserSocket du = null;
        for (UserSocket u : getUsers()) {
            if (u.name.equals(key)) {
                du = u;
                u.status = PlayerStatus.Drawer;
            } else {
                u.status = PlayerStatus.Observer;
            }
        }
        return du;
    }

    /**
     * pobierz użytkownika obsługiwanego przez ten wątek
     * @return obiekt reprezentujący danego użytkownika
     */
    private UserSocket getThisUser() {
        return getUser(myName);
    }
}
